#pragma once

#include "atomic.hpp"
#include "common.hpp"

namespace base {

namespace eec {

class shared_count {

public:
    shared_count()
        : vl_count_(1), sl_count_(1) {}

public:
    unsigned int vl_increment() {
        return vl_count_.fetch_add(1);
    }
    unsigned int vl_decrement() {
        return vl_count_.fetch_sub(1);
    }
    bool safe_increment() {
        int l_src;
        return vl_count_.not_compare_and_add(0, 1, l_src);
    }

    unsigned int sl_increment() {
        return sl_count_.fetch_add(1);
    }
    int sl_decrement() {
        return sl_count_.fetch_sub(1);
    }

private:
   atomic_int vl_count_;
   atomic_int sl_count_;
};

template<typename _T>
class weak_ptr;


template<typename _T>
class shared_ptr {
public:
    typedef _T ELEMENT_TYPE;
    typedef EEC_STD::function<void(ELEMENT_TYPE*)> DELETER;

public:
    shared_ptr() : vl_(NULL), count_(NULL) {}
    shared_ptr(_T* _vl, const DELETER& _dx = NULL) : vl_(_vl), count_(new shared_count()), dx_(_dx) {}
    shared_ptr(const shared_ptr& _rhs)
        : vl_(_rhs.vl_), count_(_rhs.count_), dx_(_rhs.dx_) {
        increment();
    }
    shared_ptr(const weak_ptr<_T>& _weak)
        : vl_(NULL), count_(NULL), dx_(NULL) {
        if (_weak.count_ && _weak.count_->safe_increment()) {
            _weak.count_->sl_increment();
            vl_ = _weak.vl_;
            count_ = _weak.count_;
            dx_ = _weak.dx_;
        }
    }
    template<class _T2>
    shared_ptr(const shared_ptr<_T2>& _rhs, _T* _data) {
        // construct shared_ptr object that aliases _Right
        this->_Alias_construct_from(_rhs, _data);
    }
    ~shared_ptr() {
        decrement();
    }

public:
    template<class _T2>
    void _Alias_construct_from(const shared_ptr<_T2>& _rhs, _T * _vl)
    {	// implement shared_ptr's aliasing ctor
        vl_ = _vl;
        count_ = _rhs.count_;
        dx_ = _rhs.dx_;
        increment();
    }

public:
    shared_ptr& operator=(const shared_ptr& _rhs) {
        if (this == &_rhs) {
            return *this;
        }
        decrement();
        vl_ = _rhs.vl_;
        count_ = _rhs.count_;
        dx_ = _rhs.dx_;
        increment();
        return *this;
    }
public:
    _T* get() const {
        return vl_;
    }
    void reset(_T* _vl, const DELETER& _dx = NULL) {
        decrement();
        vl_ = _vl;
        dx_ = _dx;
        if (NULL != vl_) {
            count_ = new shared_count();
        }
        else {
            count_ = NULL;
        }
    }
    _T* operator->() const
    {
        return vl_;
    }
    bool operator!() const
    {
        return (vl_ == NULL);
    }
    operator _T*() const
    {
        return vl_;
    }
    _T& operator*() const
    {
        return *vl_;
    }

private:
    void increment() {
        if (NULL == count_) {
            return;
        }
        count_->vl_increment();
        count_->sl_increment();
    }
    void decrement() {
        if (NULL == count_) {
            return;
        }
        if (0 == count_->vl_decrement()) {
            if (NULL != vl_) {
                if (dx_) {
                    dx_(vl_);
                }
                else {
                    delete vl_;
                }
                vl_ = NULL;
            }
        }
        if (0 == count_->sl_decrement()) {
            delete count_;
            count_ = NULL;
        } 
    }

private:
    template<class _Ty0>
    friend class weak_ptr;
    template<class _Ty0>
    friend class shared_ptr;
private:
    _T* vl_;
    shared_count* count_;
    DELETER dx_;
};

template<typename _T>
shared_ptr<_T> make_shared() {
    return shared_ptr<_T>(new _T);
}

template<typename _T>
shared_ptr<_T> make_shared(const _T& _vl) {
    return shared_ptr<_T>(new _T(_vl));
}

template<typename _T>
class weak_ptr {
public:
    typedef _T ELEMENT_TYPE;
    typedef EEC_STD::function<void(ELEMENT_TYPE*)> DELETER;

public:
    weak_ptr() : vl_(NULL), count_(NULL) {}
    weak_ptr(const weak_ptr& _rhs)
        : vl_(_rhs.vl_), count_(_rhs.count_), dx_(_rhs.dx_) {
        increment();
    }
    weak_ptr(const shared_ptr<_T>& _rhs)
        : vl_(_rhs.vl_), count_(_rhs.count_), dx_(_rhs.dx_) {
        increment();
    }
    ~weak_ptr() {
        decrement();
    }

public:
    weak_ptr& operator=(const weak_ptr& _rhs) {
        if (this == &_rhs) {
            return *this;
        }
        decrement();
        vl_ = _rhs.vl_;
        count_ = _rhs.count_;
        dx_ = _rhs.dx_;
        increment();
        return *this;
    }
    weak_ptr& operator=(const shared_ptr<_T>& _rhs) {
        decrement();
        vl_ = _rhs.vl_;
        count_ = _rhs.count_;
        dx_ = _rhs.dx_;
        increment();
        return *this;
    }

public:
    shared_ptr<_T> lock() const {
        return shared_ptr<_T>(*this);
    }

private:
    void increment() {
        if (NULL == count_) {
            return;
        }
        count_->sl_increment();
    }
    void decrement() {
        if (NULL == count_) {
            return;
        }
        if (0 == count_->sl_decrement()) {
            delete count_;
            count_ = NULL;
        }
    }

private:
    friend class shared_ptr<_T>;
private:
    _T* vl_;
    shared_count* count_;
    DELETER dx_;
};

template<class _T1, class _T2>
shared_ptr<_T1> dynamic_pointer_cast(const shared_ptr<_T2>& _rhs) {
    const auto l_data = dynamic_cast<typename shared_ptr<_T1>::ELEMENT_TYPE *>((typename shared_ptr<_T1>::ELEMENT_TYPE *)_rhs.get());
    if (l_data) {
        return (shared_ptr<_T1>(_rhs, l_data));
    }
    return (shared_ptr<_T1>());
}

}   // namespace eec

}   // namespace base